package de.jmda.core.mproc.task;

import static de.jmda.core.mproc.ProcessingUtilities.normalizeBinaryClassName;
import static de.jmda.core.mproc.ProcessingUtilities.withEnclosedTypeElements;
import static de.jmda.core.util.CollectionsUtil.asSet;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javax.lang.model.element.TypeElement;

import org.apache.log4j.Logger;
import org.junit.Test;

import de.jmda.core.util.classpath.ClasspathResource;
import de.jmda.core.util.classpath.ClasspathUtil;

/**
 * Tests basic functionality of {@link AbstractTypeElementsTaskPackages}.
 *
 * @author roger@jmda.de
 */
public class JUTTypeElementsTaskPackages
{
	private final static Logger LOGGER =
      Logger.getLogger(JUTTypeElementsTaskPackages.class);

	/**
	 * Test implementation of {@link AbstractTypeElementsTaskPackages}, collects
	 * {@link #qualifiedNames} of all type elements available via {@link
	 * #getTypeElements()}.
	 *
	 * @author roger@jmda.de
	 */
	private class TypeElementsTaskPackages
			extends AbstractTypeElementsTaskPackages
	{
		private List<String> qualifiedNames = new ArrayList<>();

		public TypeElementsTaskPackages(Set<Package> packages)
    {
	    super(packages);
    }

		@Override
		public boolean execute() throws TaskException
		{
			for (TypeElement type : withEnclosedTypeElements(getTypeElements()))
			{
				qualifiedNames.add(type.getQualifiedName().toString());
			}

			return false;
		}
	}

	/**
	 * Tests {@link AbstractTypeElementsTaskTypes} with a type only available as a
	 * .class byte code file in a .jar file in the classpath.
	 *
	 * @throws IOException
	 */
	@Test
	public void testTopLevelTypeFromJar() throws IOException
	{
		TypeElementsTaskPackages task =
				new TypeElementsTaskPackages(asSet(String.class.getPackage()));

		TaskRunner.run(task);

		assertTrue(
				"missing top level type from jar",
				task.qualifiedNames.contains(String.class.getName()));
		assertTrue(
				"too few top level types from jar", task.qualifiedNames.size() > 1);
		LOGGER.debug(
				"task found " + task.qualifiedNames.size() + " types for package " +
				String.class.getPackage().getName());
	}

	/**
	 * Tests {@link AbstractTypeElementsTaskTypes} with a top level type only
	 * available as a .class byte code file in the classpath.
	 *
	 * @throws IOException
	 */
	@Test
	public void testTopLevelTypeFromRuntime() throws IOException
	{
		TypeElementsTaskPackages task =
				new TypeElementsTaskPackages(asSet(getClass().getPackage()));

		TaskRunner.run(task);

		assertTrue(
				"missing top level type from runtime",
				task.qualifiedNames.contains(getClass().getName()));
	}

	/**
	 * Tests {@link AbstractTypeElementsTaskTypes} with an inner type only
	 * available as a .class byte code file in the classpath.
	 *
	 * @throws IOException
	 */
	@Test
	public void testInnerLevelTypeFromRuntime() throws IOException
	{
		TypeElementsTaskPackages task =
				new TypeElementsTaskPackages(
						asSet(TypeElementsTaskPackages.class.getPackage()));

		TaskRunner.run(task);

		assertTrue(
				"missing inner type from runtime",
				task.qualifiedNames.contains(
						normalizeBinaryClassName(TypeElementsTaskPackages.class)));
	}

	@Test
	public void testFindAllTypesInClasspath()
			throws
					IOException,
					InstantiationException,
					IllegalAccessException,
					ClassNotFoundException
	{
		FileFilter ignoreGuavaInResourceContainers =
				new FileFilter()
				{
					@Override
					public boolean accept(File f)
					{ return false == f.getPath().contains("guava"); }
				};

		Map<String, ClasspathResource> packagesAsClasspathResources =
				ClasspathUtil.getClasspathResources(
						ignoreGuavaInResourceContainers,
						ClasspathUtil.FILTER_ACCEPT_FILES_DIRECTORIES_ONLY,
						ClasspathUtil.FILTER_ACCEPT_ZIP_ENTRIES_DIRECTORIES_ONLY);
		
		Map<String, Package> packagesByName =
				ClasspathUtil.getPackages(packagesAsClasspathResources.keySet());

		TypeElementsTaskPackages task =
				new TypeElementsTaskPackages(
						new HashSet<Package>(packagesByName.values()));

		TaskRunner.run(task);
		
		Set<String> sortedQualifiedTypeNames = new TreeSet<>(task.qualifiedNames);
		
		for (String qualifiedTypeName : sortedQualifiedTypeNames)
		{
			LOGGER.debug("classpath type: " + qualifiedTypeName);
		}
		
		LOGGER.debug("number of classpath types: " + sortedQualifiedTypeNames.size());
	}
}